package it.unibo.refolding.pico;

import it.unibo.refolding.common.MyRuntimeException;

import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.SynchronousQueue;
import java.util.prefs.Preferences;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

import org.apache.commons.cli.Option;

public class NanoDriverUtil {
  
  private static final String RECOMMENDED_PARAMETERS = "Recommended parameters: Force->StripChartHz = Effective BW Channel for all Channels = 4464 Hz.";
	
	public static Option createDriverTypeOption() {
		Option driverTypeOption = new Option(
			"driverType", true, "stripChart or plainRefolding or approachRetraction");
		driverTypeOption.setRequired(true);
		return driverTypeOption;
	}

	public static Option createNanoDriverClassOption() {
		Option nanoDriverClassOption = new Option("nanoDriverClass", true,
				"it.unibo.refolding.pico.RobotNanoDriver or it.unibo.refolding.pico.FakeNanoDriver");
		nanoDriverClassOption.setRequired(true);
		nanoDriverClassOption.setType(String.class);
		return nanoDriverClassOption;
	}

	public static boolean isFakeNanoDriver(String nanoDriverClassname) {
		return nanoDriverClassname.equals("it.unibo.refolding.pico.FakeNanoDriver");
	}

  public static NanoDriverConfig createFetchRefoldBreakRobotConfig(boolean forceRecord) {
    System.out.println(RECOMMENDED_PARAMETERS);
    List<Point> points = NanoDriverUtil.recordOrFetchScreenLocations(
      Arrays.asList(new String[] {
      "Start Ramp Column 1", "End Ramp Column 1", "Run Script", "Capture Button",
      "Start Strip Chart", "Quit Strip Chart"}), forceRecord);
    NanoDriverConfig config = new NanoDriverConfig();
    int i = 0;
    config.startRampColumn1Location = points.get(i++);
    config.endRampColumn1Location = points.get(i++);
    config.runScriptLocation = points.get(i++);
    config.captureButtonLocation = points.get(i++);
    config.startStripChartLocation = points.get(i++);
    config.quitStripChartLocation = points.get(i++);

    return config;
  }

	public static NanoDriverConfig createStripChartRefoldingRobotConfig(boolean forceRecord) {
		System.out.println(RECOMMENDED_PARAMETERS);
		List<Point> points = recordOrFetchScreenLocations(Arrays.asList(new String[] {
			"Force Mode", "Single", "X Offset", "ZScanStart", "Script Mode",
			// "Rel X Offset Column 1",
			"Start Ramp Column 1", "End Ramp Column 1", "Velocity Column 1", "Delay Column 1",
			"Delta Column 2", "Velocity Column 2", "Delta Column 3", "Velocity Column 3", "Loop Count Column 3",
			"Run Script", "Capture Button", "Start Strip Chart", "Quit Strip Chart", "Reset"}), forceRecord);
		NanoDriverConfig config = new NanoDriverConfig();
		int i = 0;
	
		config.forceModeLocation = points.get(i++);
		config.singleLocation = points.get(i++);
		config.xOffsetLocation = points.get(i++);
		config.zScanStartLocation = points.get(i++);
		config.scriptModeLocation = points.get(i++);
	
		config.startRampColumn1Location = points.get(i++);
		config.endRampColumn1Location = points.get(i++);
		config.velocityColumn1Location = points.get(i++);
		config.delayColumn1Location = points.get(i++);
	
		config.deltaColumn2Location = points.get(i++);
		config.velocityColumn2Location = points.get(i++);
    config.deltaColumn3Location = points.get(i++);
    config.velocityColumn3Location = points.get(i++);
		
		config.loopCountColumn3Location = points.get(i++);
	
		config.runScriptLocation = points.get(i++);
		config.captureButtonLocation = points.get(i++);
		config.startStripChartLocation = points.get(i++);
		config.quitStripChartLocation = points.get(i++);
		config.resetStripChartLocation = points.get(i++);
	
		assert points.size() == i;
		return config;
	}

  public static NanoDriverConfig createApproachRetractionRobotConfig(boolean forceRecord) {
    List<Point> points = recordOrFetchScreenLocations(
      Arrays.asList(new String[] {
        "Reverse Velocity", "ZScanStart",
        "X offset", "Continuous",
        }), forceRecord);
    NanoDriverConfig config = new NanoDriverConfig();
    int i = 0;
    config.reverseVelocityLocation = points.get(i++);
    config.zScanStartLocation = points.get(i++);
    config.xOffsetLocation = points.get(i++);
    config.continuousLocation = points.get(i++);
    return config;
  }
  
	public static NanoDriverConfig createPlainRefoldingRobotConfig(boolean forceRecord) {
		List<Point> points = recordOrFetchScreenLocations(
			Arrays.asList(new String[] {
				"RampSize", "ScanRate", "Number of samples", "ZScanStart", "SurfaceDelay",
				"X offset", "Single", "Continuous", "Stop",
				// Script Chart locations to lose the molecule.
				"Force Mode", "Script Mode", "Realtime", "Capture Continuous", "Abort Capture",
				"Start Ramp Column 1", "End Ramp Column 1", "Velocity Column 1", "Delay Column 1",
				"Delta Column 2", "Velocity Column 2", "Loop Count Column 2",
				"Run Script", "Capture Button", "Start Strip Chart", "Reset", "Quit Strip Chart"
				}), forceRecord);
		NanoDriverConfig config = new NanoDriverConfig();
		int i = 0;
		
		config.rampSizeLocation = points.get(i++);
		config.scanRateLocation = points.get(i++);
		config.numberOfSamplesLocation = points.get(i++);
		config.zScanStartLocation = points.get(i++);
		config.surfaceDelayLocation = points.get(i++);
		config.xOffsetLocation = points.get(i++);
		config.singleLocation = points.get(i++);
		config.continuousLocation = points.get(i++);
		config.stopLocation = points.get(i++);

		config.forceModeLocation = points.get(i++);
		config.scriptModeLocation = points.get(i++);
		config.realtimeLocation = points.get(i++);
		config.captureContinuousLocation = points.get(i++);
		config.abortCaptureLocation = points.get(i++);
		
		config.startRampColumn1Location = points.get(i++);
		config.endRampColumn1Location = points.get(i++);
		config.velocityColumn1Location = points.get(i++);
		config.delayColumn1Location = points.get(i++);
	
		config.deltaColumn2Location = points.get(i++);
		config.velocityColumn2Location = points.get(i++);
		config.loopCountColumn3Location = points.get(i++);
	
		config.runScriptLocation = points.get(i++);
		config.captureButtonLocation = points.get(i++);
		config.startStripChartLocation = points.get(i++);
		config.resetStripChartLocation = points.get(i++);
		config.quitStripChartLocation = points.get(i++);

		return config;
	}

	public static List<Point> recordOrFetchScreenLocations(final List<String> labels,
			boolean forceRecord) {
		Preferences userNodeForPackage = Preferences.userNodeForPackage(NanoDriverUtil.class);
		List<Point> fromPreferences = new ArrayList<Point>();
		for (String label : labels) {
			int x = userNodeForPackage.getInt(label + ".x", -1);
			int y = userNodeForPackage.getInt(label + ".y", -1);
			if (x == -1 || y == -1) {
				break;
			} else {
				fromPreferences.add(new Point(x, y));
			}
		}
		final List<Point> points;
		if (forceRecord || fromPreferences.size() != labels.size()) {
			final SynchronousQueue<List<Point>> singleton = new SynchronousQueue<List<Point>>();
			try {
				SwingUtilities.invokeAndWait(new Runnable() {
					public void run() {
						final JFrame frame = new JFrame("Teach me");
						frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
						final JLabel label = new JLabel();
						frame.getContentPane().add(label);
						// frame.setPreferredSize(Toolkit.getDefaultToolkit().getScreenSize());
						// frame.setMinimumSize(Toolkit.getDefaultToolkit().getScreenSize());
						// frame.setMaximumSize(Toolkit.getDefaultToolkit().getScreenSize());
						label.setText(String.format("Drag me onto: %s", labels.get(0)));
						label.setFont(label.getFont().deriveFont(30f));
						final List<Point> mouseReleasePoints = new ArrayList<Point>();
						label.addMouseListener(new MouseAdapter() {
							@Override
							public void mouseReleased(MouseEvent e) {
								mouseReleasePoints.add(e.getLocationOnScreen());
								if (mouseReleasePoints.size() == labels.size()) {
									frame.dispose();
									try {
										singleton.put(mouseReleasePoints);
									} catch (InterruptedException e1) {
										e1.printStackTrace();
										System.exit(1);
									}
								} else {
									label.setText(String.format("Drag me onto %s.", labels
											.get(mouseReleasePoints.size())));
									frame.pack();
								}
								// System.out.println(e);
							}
						});
						frame.pack();
						frame.setVisible(true);
					}
				});
			} catch (InvocationTargetException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			} catch (InterruptedException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			}
			try {
				points = singleton.take();
			} catch (InterruptedException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			}
			for (int i = 0; i < labels.size(); ++i) {
				userNodeForPackage.putInt(labels.get(i) + ".x", points.get(i).x);
				userNodeForPackage.putInt(labels.get(i) + ".y", points.get(i).y);
			}
		} else {
			points = fromPreferences;
		}
		return points;
	}

	public static Option createForceRecordOption() {
		Option forceRecordOption = new Option("forceRecord", true,
				"Record the screen location even if already found in user preferences..");
		forceRecordOption.setRequired(true);
		forceRecordOption.setType(Double.TYPE);
		return forceRecordOption;
	}

	public static NanoDriver localNanoDriver(
		String nanoDriverClassname, NanoDriverConfig config) {
		if (isFakeNanoDriver(nanoDriverClassname)) {
			return new FakeNanoDriver(config);
		} else {
			try {
				return (NanoDriver) Class.forName(nanoDriverClassname).getConstructor(
						NanoDriverConfig.class).newInstance(config);
			} catch (InstantiationException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			} catch (IllegalAccessException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			} catch (InvocationTargetException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			} catch (NoSuchMethodException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			} catch (ClassNotFoundException e) {
				throw new MyRuntimeException(e.getMessage(), e);
			}
		}
	}

	public static Option createNanoDriverHostOption() {
		Option nanoDriverHostOption = new Option("nanoDriverHost", true,"");
		nanoDriverHostOption.setRequired(true);
		nanoDriverHostOption.setType(String.class);
		return nanoDriverHostOption;
	}

	public static Option createNanoDriverPortOption() {
		Option nanoDriverPortOption = new Option("nanoDriverPort", true,"");
		nanoDriverPortOption.setRequired(true);
		nanoDriverPortOption.setType(Long.class);
		return nanoDriverPortOption;
	}
}
